<?php

// @formatter:off
/**
 * @file WCDirectoryOrganizer.php
 * @author Alejandro Dario Simi
 * @date $Date: 2014-05-04 01:33:52 +0000 (Sun, 04 May 2014) $
 *
 * $Id: WCDirectoryOrganizer.php 111 2014-05-04 01:33:52Z daemonraco@gmail.com $
 * $URL: http://wcomix.googlecode.com/svn/trunk/includes/rules/WCDirectoryOrganizer.php $
 */
// @formatter:on

class WCDirectoryOrganizer extends WCRuleAction {
	//
	// Public class properties.
	public static $TYPE = "DOR";
	public static $ModeMove = "MOVE";
	public static $ModeCMove = "CMOVE";
	public static $ModeTagger = "TAGGER";
	public static $ModeCollector = "COLLECTOR";
	public static $ModeVMove = "VMOVE";
	public static $StatusEnabled = "E";
	public static $StatusDisabled = "D";
	//
	// Protected properties.
	protected $_collections = array();
	protected $_directories = array();
	protected $_previews = array();
	protected $_previewsLoaded = false;
	protected $_tags = array();
	//
	// Public methods.
	public function collection() {
		$out = null;
		switch(count($this->_collections)) {
			case 0:
				break;
			case 1:
				$key = array_keys($this->_collections);
				$key = array_shift($key);
				$out = $this->_collections[$key];
				break;
			default:
				$this->setConflict(true);
				break;
		}
		return $out;
	}
	public function directory() {
		$out = null;
		switch(count($this->_directories)) {
			case 0:
				break;
			case 1:
				$key = array_keys($this->_directories);
				$key = array_shift($key);
				$out = $this->_directories[$key];
				break;
			default:
				$this->setConflict(true);
				break;
		}
		return $out;
	}
	public function generatePreviews() {
		$out = false;

		$previewsManager = WCPreviewsManager::I();
		$previews = array();
		foreach($this->_tags as $tag) {
			$previews = array_merge($previews, $previewsManager->getPreviews($this->group(), WCPreviewsManager::TypeTag, $tag->id()));
		}
		foreach($this->_directories as $dir) {
			$previews = array_merge($previews, $previewsManager->getPreviews($this->group(), WCPreviewsManager::TypeDirectory, $dir->id()));
		}
		foreach($this->_collections as $col) {
			$previews = array_merge($previews, $previewsManager->getPreviews($this->group(), WCPreviewsManager::TypeCollection, $col->id()));
		}

		$previews = array_unique($previews);

		$position = 0;
		foreach($previews as $preview) {
			if($previewsManager->addPreviews($this->group(), WCPreviewsManager::TypeRuleAction, $this->id(), $position, $preview)) {
				$position++;
			}

			if($position >= WCPreviewsManager::MaxPerRuleAction) {
				break;
			}
		}

		if(count($previews) > 0) {
			$this->_previewsLoaded = false;
			$out = true;
		}
		return $out;
	}
	public function mode() {
		return $this->mode;
	}
	public function reload() {
		$this->_tags = array();
		$this->_directories = array();
		$this->_collections = array();

		parent::reload();
	}
	public function run() {
		$out = array();

		if($this->status == self::$StatusEnabled) {
			switch($this->mode) {
				case self::$ModeMove:
					$out = $this->runModeMoveComic();
					$out = $this->runModeMoveImage();
					break;
				case self::$ModeCMove:
					$out = $this->runModeCMoveComic();
					$out = $this->runModeCMoveImage();
					break;
				case self::$ModeTagger:
					$out = $this->runModeTaggerComic();
					$out = $this->runModeTaggerImage();
					break;
				case self::$ModeCollector:
					$out = $this->runModeCollectorComic();
					$out = $this->runModeCollectorImage();
					break;
				case self::$ModeVMove:
					$out = $this->runModeVMoveComic();
					$out = $this->runModeVMoveImage();
					break;
			}
		} else {
			$out = array("items" => array(array(
						"action" => "Action '{$this->name()}' ({$this->_id}[{$this->_ruleId}]) is disabled",
						"member" => false,
						"status" => false,
						"fatal" => false
			)));
		}

		return $out;
	}
	public function tags($sorted = false) {
		if($sorted) {
			uasort($this->_tags, "WCTag::NameSort");
		}
		return $this->_tags;
	}
	public function words() {
		$out = array();

		foreach($this->_tags as $item) {
			$out = array_merge($out, WCSearch::StringToPatterns($item->name));
		}
		foreach($this->_directories as $item) {
			$out = array_merge($out, WCSearch::StringToPatterns($item->name));
		}
		foreach($this->_collections as $item) {
			$out = array_merge($out, WCSearch::StringToPatterns($item->name));
		}

		return $out;
	}
	//
	// Protected methods.
	protected function enforceMode() {
		$stmt = null;
		$stmtId = __CLASS__."::".__FUNCTION__;
		if($this->_db->has($stmtId)) {
			$stmt = $this->_db->get($stmtId);
		} else {
			$query = "update  {$this->_dbprefix}acts_dor\n";
			$query .= "set     act_mode = :mode\n";
			$query .= "where   act_rule   = :rule\n";
			$query .= " and    act_group  = :group\n";
			$query .= " and    act_action = :id\n";
			$stmt = $this->_db->prepare($stmtId, $query);
		}

		$params = array(
			":rule" => $this->_ruleId,
			":group" => $this->_group->id,
			":id" => $this->_id,
			":mode" => $this->mode
		);
		$stmt->execute($params);
	}
	protected function load() {
		if($this->_type != self::$TYPE) {
			trigger_error("Loaded type and class type are different.", E_USER_ERROR);
		}

		$stmt = null;
		$stmtId = __CLASS__."::".__FUNCTION__."[1]";
		if($this->_db->has($stmtId)) {
			$stmt = $this->_db->get($stmtId);
		} else {
			$query = "select  *\n";
			$query .= "from    {$this->_dbprefix}acts_dor\n";
			$query .= "where   act_rule   = :rule\n";
			$query .= " and    act_group  = :group\n";
			$query .= " and    act_action = :id\n";
			$stmt = $this->_db->prepare($stmtId, $query);
		}

		$params = array(
			":rule" => $this->_ruleId,
			":group" => $this->_group->id,
			":id" => $this->_id
		);
		if($stmt->execute($params) && $stmt->rowCount() > 0) {
			global $wcCollectionsHolder;

			foreach($stmt->fetchAll() as $row) {
				if($row["act_tag"] && !isset($this->_tags[$row["act_tag"]])) {
					$tag = WCTagsHolder::I()->item($row["act_tag"]);
					if($tag && $tag->ok()) {
						$this->_tags[$tag->id] = $tag;
					}
				}

				if($row["act_directory"] && !isset($this->_tags[$row["act_directory"]])) {
					$directory = WCDirectoriesHolder::I()->item($row["act_directory"]);
					if($directory && $directory->ok()) {
						$this->_directories[$directory->id] = $directory;
					}
				}
				$this->setConflict($this->_conflict || count($this->_directories) > 1);

				if($row["act_collection"] && !isset($this->_tags[$row["act_collection"]])) {
					$collection = $wcCollectionsHolder->item($row["act_collection"]);
					if($collection && $collection->ok()) {
						$this->_collections[$collection->id] = $collection;
					}
				}
				$this->setConflict($this->_conflict || count($this->_collections) > 1);

				if($row["act_mode"] != $this->mode) {
					$this->enforceMode();
				}
			}
		}

		$this->_ok = true;

		if(!$this->mode) {
			$this->mode = self::$ModeTagger;
		}
		if(!$this->status) {
			$this->status = self::$StatusEnabled;
		}
	}
	protected function moveComicTo(WCComic &$comic, WCDirectory &$auxDir) {
		$aux = array(
			"action" => "Moving '{$comic->name}' ({$comic->id}) from '{$comic->directory()->name}' ({$comic->directory}) to directory '{$auxDir->name}' ({$auxDir->id})",
			"member" => $comic,
			"fatal" => true
		);

		$futurePath = WCSanitizer::SystemPath("{$auxDir->fullPath()}/{$comic->name}");
		if(!file_exists($futurePath)) {
			$aux["status"] = $comic->setDirectory($auxDir);
		} else {
			$aux["status"] = false;
			$aux["error"] = "Path '{$futurePath}' already exists";
		}

		return $aux;
	}
	protected function moveImageTo(WCImage &$image, WCDirectory &$auxDir) {
		$aux = array(
			"action" => "Moving '{$image->name}' ({$image->id}) from '{$image->directory()->name}' ({$image->directory}) to directory '{$auxDir->name}' ({$auxDir->id})",
			"member" => $image,
			"fatal" => true
		);

		$futurePath = WCSanitizer::SystemPath("{$auxDir->fullPath()}/{$image->name}");
		if(!file_exists($futurePath) && is_writable($auxDir->fullPath())) {
			$aux["status"] = $image->setDirectory($auxDir);
		} elseif(!is_writable($auxDir->fullPath())) {
			$aux["status"] = false;
			$aux["error"] = "Unable to write into '{$auxDir->fullPath()}'";
		} else {
			$aux["status"] = false;
			$aux["error"] = "Path '{$futurePath}' already exists";
		}

		return $aux;
	}
	protected function removeSpecific() {
		$out = true;

		$stmt = null;
		$stmtId = __CLASS__."::".__FUNCTION__;
		if($this->_db->has($stmtId)) {
			$stmt = $this->_db->get($stmtId);
		} else {
			$query = "delete from {$this->_dbprefix}acts_dor\n";
			$query .= "where       act_rule   = :rule\n";
			$query .= " and        act_group  = :group\n";
			$query .= " and        act_action = :id\n";
			$stmt = $this->_db->prepare($stmtId, $query);
		}

		$params = array(
			":rule" => $this->_ruleId,
			":group" => $this->_group->id,
			":id" => $this->_id
		);

		if(!$stmt->execute($params)) {
			$out = false;
		}

		return $out;
	}
	protected function runModeCMoveComic() {
		$out = array();

		if($this->ok() && !$this->conflict() && $this->directory()) {
			if($this->directory() && $this->directory()->ok() && $this->collection() && $this->collection()->ok()) {
				$auxDir = $this->directory();
				$auxCol = $this->collection();
				//
				// Checking every comic of the collection.
				foreach($auxCol->comics() as $comic) {
					//
					// Checking if the selected comic
					// is not in the directory.
					if(!$comic->isTrashed() && $comic->directory != $auxDir->id) {
						if(!isset($out["items"])) {
							$out["items"] = array();
						}
						//
						// It seems not to be present,
						// therefore it is moved.
						$out["items"][] = $this->moveComicTo($comic, $auxDir);
					}
				}
			}
		}

		return $out;
	}
	protected function runModeCollectorComic() {
		$out = array();

		if($this->ok() && !$this->conflict() && $this->directory()) {
			$idMembers = array();

			if($this->directory() && $this->directory()->ok()) {
				$collection = $this->collection();
				//
				// Checking every comic in the directory.
				foreach($this->directory()->comicsFull() as $comic) {
					//
					// Checking comic collection.
					if($comic->collection != $collection->id) {
						$comic->collection = $collection->id;

						$aux = array(
							"action" => "Adding '{$comic->name}' ({$comic->id}[{$comic->directory}]) to collection '{$collection->name}' ({$collection->id})",
							"member" => $comic,
							"status" => true,
							"fatal" => true
						);
						if(!isset($out["items"])) {
							$out["items"] = array();
						}
						$out["items"][] = $aux;
					}
				}
			}
		}

		return $out;
	}
	protected function runModeCollectorImage() {
		$out = array();

		if($this->ok() && !$this->conflict() && $this->directory()) {
			$idMembers = array();

			if($this->directory() && $this->directory()->ok()) {
				$collection = $this->collection();
				//
				// Checking every image in the directory.
				foreach($this->directory()->imagesFull() as $image) {
					//
					// Checking image collection.
					if($image->collection != $collection->id) {
						$image->collection = $collection->id;

						$aux = array(
							"action" => "Adding image {$image} to collection {$collection}",
							"member" => $image,
							"status" => true,
							"fatal" => true
						);
						if(!isset($out["items"])) {
							$out["items"] = array();
						}
						$out["items"][] = $aux;
					}
				}
			}
		}

		return $out;
	}
	protected function runModeMoveComic() {
		$out = array();

		if($this->ok() && !$this->conflict() && $this->directory()) {
			if($this->directory() && $this->directory()->ok()) {
				$auxDir = $this->directory();
				//
				// Checking every tag.
				foreach($this->tags() as $tag) {
					//
					// Checking every comic of the current
					// tag.
					foreach($tag->comicsFull() as $comic) {
						//
						// Checking if the selected comic
						// is not in the directory.
						if(!$comic->isTrashed() && $comic->directory != $auxDir->id) {
							if(!isset($out["items"])) {
								$out["items"] = array();
							}
							//
							// It seems not to be
							// present, therefore it
							// is moved.
							$out["items"][] = $this->moveComicTo($comic, $auxDir);
						}
					}
				}
			}
		}

		return $out;
	}
	protected function runModeTaggerComic() {
		$out = array();

		if($this->ok() && !$this->conflict() && $this->directory()) {
			$idMembers = array();

			if($this->directory() && $this->directory()->ok()) {
				//
				// Checking every comic in the directory.
				foreach($this->directory()->comicsFull() as $comic) {
					//
					// Checking tags in the comic.
					foreach($this->tags() as $tag) {
						//
						// Caching comics ids to gain
						// performance.
						if(!isset($idMembers[$tag->id])) {
							$idMembers[$tag->id] = $tag->comics();
						}
						//
						// Checking if the selected comic
						// has the tag.
						if(!in_array($comic->id, $idMembers[$tag->id])) {
							//
							// It seems not to be
							// present, therefore it
							// is added.
							$aux = array(
								"action" => "Adding '{$comic->name}' ({$comic->id}[{$comic->directory}]) to tag '{$tag->name}' ({$tag->id})",
								"member" => $comic,
								"status" => true,
								"fatal" => true
							);
							$tag->addComic($comic);
							if(!isset($out["items"])) {
								$out["items"] = array();
							}
							$out["items"][] = $aux;

							unset($idMembers[$tag->id]);
						}
					}
				}
			}
		}

		return $out;
	}
	protected function runModeVMoveComic() {
		$out = array();

		if($this->ok() && !$this->conflict() && $this->directory()) {
			if($this->directory() && $this->directory()->ok()) {
				$auxDir = $this->directory();
				//
				// Checking every tag.
				foreach($this->tags() as $tag) {
					//
					// Checking every comic of the current
					// tag.
					foreach($tag->comicsFull() as $comic) {
						$dontRemove = false;

						if(!isset($out["items"])) {
							$out["items"] = array();
						}
						//
						// Checking if the selected comic
						// is not in the directory.
						if(!$comic->isTrashed() && $comic->directory != $auxDir->id) {
							//
							// It seems not to be
							// present, therefore it
							// is moved.
							$aux = $this->moveComicTo($comic, $auxDir);
							$out["items"][] = $aux;
							$dontRemove = !$aux["status"];
						}
						//
						// Tag was already check,
						// therefore, it is removed.
						if(!$dontRemove) {
							$aux = array(
								"action" => "Removing '{$comic->name}' ({$comic->id}[{$comic->directory}]) from tag '{$tag->name}' ({$tag->id})",
								"member" => $comic,
								"fatal" => true
							);
							$aux["status"] = $tag->removeComic($comic);
							$out["items"][] = $aux;
						}
					}
				}
			}
		}

		return $out;
	}
	protected function runModeCMoveImage() {
		$out = array();

		if($this->ok() && !$this->conflict() && $this->directory()) {
			if($this->directory() && $this->directory()->ok() && $this->collection() && $this->collection()->ok()) {
				$auxDir = $this->directory();
				$auxCol = $this->collection();
				//
				// Checking every image of the collection.
				foreach($auxCol->images() as $image) {
					//
					// Checking if the selected image
					// is not in the directory.
					if(!$image->isTrashed() && $image->directory != $auxDir->id) {
						if(!isset($out["items"])) {
							$out["items"] = array();
						}
						//
						// It seems not to be present,
						// therefore it is moved.
						$out["items"][] = $this->moveImageTo($image, $auxDir);
					}
				}
			}
		}

		return $out;
	}
	protected function runModeMoveImage() {
		$out = array();

		if($this->ok() && !$this->conflict() && $this->directory()) {
			if($this->directory() && $this->directory()->ok()) {
				$auxDir = $this->directory();
				//
				// Checking every tag.
				foreach($this->tags() as $tag) {
					//
					// Checking every image of the current
					// tag.
					foreach($tag->imagesFull() as $image) {
						//
						// Checking if the selected image
						// is not in the directory.
						if(!$image->isTrashed() && $image->directory != $auxDir->id) {
							if(!isset($out["items"])) {
								$out["items"] = array();
							}
							//
							// It seems not to be
							// present, therefore it
							// is moved.
							$out["items"][] = $this->moveImageTo($image, $auxDir);
						}
					}
				}
			}
		}

		return $out;
	}
	protected function runModeTaggerImage() {
		$out = array();

		if($this->ok() && !$this->conflict() && $this->directory()) {
			$idMembers = array();

			if($this->directory() && $this->directory()->ok()) {
				//
				// Checking every image in the directory.
				foreach($this->directory()->imagesFull() as $image) {
					//
					// Checking tags in the image.
					foreach($this->tags() as $tag) {
						//
						// Caching images ids to gain
						// performance.
						if(!isset($idMembers[$tag->id])) {
							$idMembers[$tag->id] = $tag->images();
						}
						//
						// Checking if the selected image
						// has the tag.
						if(!in_array($image->id, $idMembers[$tag->id])) {
							//
							// It seems not to be
							// present, therefore it
							// is added.
							$aux = array(
								"action" => "Adding '{$image->name}' ({$image->id}[{$image->directory}]) to tag '{$tag->name}' ({$tag->id})",
								"member" => $image,
								"status" => true,
								"fatal" => true
							);
							$tag->addImage($image);
							if(!isset($out["items"])) {
								$out["items"] = array();
							}
							$out["items"][] = $aux;

							unset($idMembers[$tag->id]);
						}
					}
				}
			}
		}

		return $out;
	}
	protected function runModeVMoveImage() {
		$out = array();

		if($this->ok() && !$this->conflict() && $this->directory()) {
			if($this->directory() && $this->directory()->ok()) {
				$auxDir = $this->directory();
				//
				// Checking every tag.
				foreach($this->tags() as $tag) {
					//
					// Checking every image of the current
					// tag.
					foreach($tag->imagesFull() as $image) {
						$dontRemove = false;

						if(!isset($out["items"])) {
							$out["items"] = array();
						}
						//
						// Checking if the selected image
						// is not in the directory.
						if(!$image->isTrashed() && $image->directory != $auxDir->id) {
							//
							// It seems to be not
							// present, therefore it
							// is moved.
							$aux = $this->moveImageTo($image, $auxDir);
							$out["items"][] = $aux;
							$dontRemove = !$aux["status"];
						}
						//
						// Tag was already check,
						// therefore, it is removed.
						if(!$dontRemove) {
							$aux = array(
								"action" => "Removing '{$image->name}' ({$image->id}[{$image->directory}]) from tag '{$tag->name}' ({$tag->id})",
								"member" => $image,
								"fatal" => true
							);
							$aux["status"] = $tag->removeImage($image);
							$out["items"][] = $aux;
						}
					}
				}
			}
		}

		return $out;
	}
	//
	// Class methods.
	public static function LockedTagIds(WCGroup &$group) {
		$out = array();

		$db = WCConnection::Instance();
		$dbprefix = $db->prefix();

		$stmt = null;
		$stmtId = __CLASS__."::".__FUNCTION__;
		if($db->has($stmtId)) {
			$stmt = $db->get($stmtId);
		} else {
			$query = "select  distinct(act_tag) as tag\n";
			$query .= "from    {$dbprefix}acts_dor\n";

			$stmt = $db->prepare($stmtId, $query);
		}

		if($stmt->execute()) {
			foreach($stmt->fetchAll() as $row) {
				$out[] = $row["tag"];
			}
		}

		return array_unique($out);
	}
	public static function MarkConflicts() {
		$out = 0;

		$db = WCConnection::Instance();
		$dbprefix = $db->prefix();
		//
		// Looking for tags that move contents to two different
		// directories.
		$stmt = null;
		$stmtId = __CLASS__."::".__FUNCTION__."[MOVE-MULTI_DIR]";
		if($db->has($stmtId)) {
			$stmt = $db->get($stmtId);
		} else {
			$query = "update  {$dbprefix}rule_actions\n";
			$query .= "set     rel_ra_conflict = 'Y'\n";
			$query .= "where   (rel_ra_rule,rel_ra_action) in ((\n";
			$query .= "                select  act_rule,\n";
			$query .= "                        act_action\n";
			$query .= "                from    {$dbprefix}acts_dor\n";
			$query .= "                where   act_tag in (\n";
			$query .= "                                select  act_tag\n";
			$query .= "                                from    {$dbprefix}acts_dor\n";
			$query .= "                                where   act_mode = 'MOVE'\n";
			$query .= "                                group by act_tag\n";
			$query .= "                                having count(distinct(act_directory)) > 1)\n";
			$query .= "        ) union all (\n";
			$query .= "                select  act_rule,\n";
			$query .= "                        act_action\n";
			$query .= "                from    {$dbprefix}acts_dor\n";
			$query .= "                where   act_collection in (\n";
			$query .= "                                select  act_collection\n";
			$query .= "                                from    {$dbprefix}acts_dor\n";
			$query .= "                                where   act_mode = 'CMOVE'\n";
			$query .= "                                group by act_collection\n";
			$query .= "                                having count(distinct(act_directory)) > 1)\n";
			$query .= "        ))\n";

			$stmt = $db->prepare($stmtId, $query);
		}

		if($stmt->execute()) {
			$out += $stmt->rowCount();
		}
		//
		// Looking for collections that move contents to two different
		// directories.
		$stmt = null;
		$stmtId = __CLASS__."::".__FUNCTION__."[CMOVE-MULTI_DIR]";
		if($db->has($stmtId)) {
			$stmt = $db->get($stmtId);
		} else {
			$query = "update  {$dbprefix}rule_actions\n";
			$query .= "set     rel_ra_conflict = 'Y'\n";
			$query .= "where   (rel_ra_rule,rel_ra_action) in (\n";
			$query .= "                select  act_rule,\n";
			$query .= "                        act_action\n";
			$query .= "                from    {$dbprefix}acts_dor\n";
			$query .= "                where   act_collection in (\n";
			$query .= "                                select  act_collection\n";
			$query .= "                                from    {$dbprefix}acts_dor\n";
			$query .= "                                where   act_mode = 'CMOVE'\n";
			$query .= "                                group by act_collection\n";
			$query .= "                                having count(distinct(act_directory)) > 1))\n";

			$stmt = $db->prepare($stmtId, $query);
		}

		if($stmt->execute()) {
			$out += $stmt->rowCount();
		}

		return $out;
	}
}
